﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media.Animation;
using Tww.Wpf.Controls.Controls.Glows;
using Tww.Wpf.Controls.Native.Models.Win32;

namespace Tww.Wpf.Controls.Controls.Windows
{
    /// <summary>
    /// Interaktionslogik für GlowWindow.xaml
    /// </summary>
    public partial class GlowWindow : Window
    {
        #region  - Felder (privat) -

        private static double? _dpiFactor = null;
        private readonly Func<Point, Cursor> getCursor;
        private readonly Func<double, double> getHeight;
        private readonly Func<Point, HitTestValues> getHitTestValue;
        private readonly Func<double, double> getLeft;
        private readonly Func<double, double> getTop;
        private readonly Func<double, double> getWidth;
        private bool closing = false;
        private IntPtr handle;
        private IntPtr ownerHandle;

        #endregion

        #region  - Konstruktoren -

        public GlowWindow(Window owner, GlowDirection direction)
        {
            InitializeComponent();
            AllowsTransparency = true;
            Owner = owner;
            glow.Visibility = Visibility.Collapsed;

            var glowBinding = new Binding("GlowBrush") {Source = owner};
            glow.SetBinding(Glow.GlowBrushProperty, glowBinding);

            glowBinding = new Binding("NonActiveGlowBrush") {Source = owner};
            glow.SetBinding(Glow.NonActiveGlowBrushProperty, glowBinding);

            getHitTestValue = p => GetWindowHitTestValue(p, direction);
            getCursor = p => GetWindowEdgeCursor(owner, p, direction);

            switch (direction)
            {
                case GlowDirection.Left:
                    glow.Orientation = Orientation.Vertical;
                    glow.HorizontalAlignment = HorizontalAlignment.Right;
                    getLeft = (dpi) => Math.Round((owner.Left - glowSize)*dpi);
                    getTop = (dpi) => Math.Round((owner.Top - glowSize)*dpi);
                    getWidth = (dpi) => glowSize*dpi;
                    getHeight = (dpi) => (owner.ActualHeight + glowSize*2.0)*dpi;
                    break;
                case GlowDirection.Right:
                    glow.Orientation = Orientation.Vertical;
                    glow.HorizontalAlignment = HorizontalAlignment.Left;
                    getLeft = (dpi) => Math.Round((owner.Left + owner.ActualWidth)*dpi);
                    getTop = (dpi) => Math.Round((owner.Top - glowSize)*dpi);
                    getWidth = (dpi) => glowSize*dpi;
                    getHeight = (dpi) => (owner.ActualHeight + glowSize*2.0)*dpi;
                    break;
                case GlowDirection.Top:
                    glow.Orientation = Orientation.Horizontal;
                    glow.VerticalAlignment = VerticalAlignment.Bottom;
                    getLeft = (dpi) => owner.Left*dpi;
                    getTop = (dpi) => Math.Round((owner.Top - glowSize)*dpi);
                    getWidth = (dpi) => Math.Round(owner.ActualWidth*dpi);
                    getHeight = (dpi) => glowSize*dpi;
                    break;
                case GlowDirection.Bottom:
                    glow.Orientation = Orientation.Horizontal;
                    glow.VerticalAlignment = VerticalAlignment.Top;
                    getLeft = (dpi) => owner.Left*dpi;
                    getTop = (dpi) => Math.Round((owner.Top + owner.ActualHeight)*dpi);
                    getWidth = (dpi) => Math.Round(owner.ActualWidth*dpi);
                    getHeight = (dpi) => glowSize*dpi;
                    break;
            }

            owner.ContentRendered += (sender, e) => glow.Visibility = Visibility.Visible;
            owner.Activated += (sender, e) =>
            {
                Update();
                glow.IsGlow = true;
            };
            owner.Deactivated += (sender, e) => glow.IsGlow = false;
            owner.LocationChanged += (sender, e) => Update();
            owner.SizeChanged += (sender, e) => Update();
            owner.StateChanged += (sender, e) => Update();
            owner.IsVisibleChanged += (sender, args) => Update();

            owner.Closed += (sender, e) =>
            {
                closing = true;
                Close();
            };
        }

        #endregion

        #region  - Indexer und Eigenschaften (öffentlich) -

        public double DpiFactor
        {
            get
            {
                if (_dpiFactor == null)
                {
                    double dpiX = 96.0, dpiY = 96.0;

                    // #652, #752 check if Owner not null
                    Window owner = Owner ?? (Application.Current != null ? Application.Current.MainWindow : null);
                    PresentationSource source = owner != null ? PresentationSource.FromVisual(owner) : null;
                    if (source != null && source.CompositionTarget != null)
                    {
                        dpiX = 96.0*source.CompositionTarget.TransformToDevice.M11;
                        dpiY = 96.0*source.CompositionTarget.TransformToDevice.M22;
                    }

                    _dpiFactor = dpiX == dpiY ? dpiX/96.0 : 1;
                }
                return _dpiFactor.Value;
            }
        }

        public Storyboard OpacityStoryboard { get; set; }

        #endregion

        #region  - Methoden (öffentlich) -

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            OpacityStoryboard = TryFindResource("OpacityStoryboard") as Storyboard;
        }

        public void Update()
        {
            if (Owner.Visibility == Visibility.Hidden)
            {
                Visibility = Visibility.Hidden;

                UpdateCore();
            }
            else if (Owner.WindowState == WindowState.Normal)
            {
                if (closing) return;

                Visibility = Visibility.Visible;

                UpdateCore();
            }
            else
            {
                Visibility = Visibility.Collapsed;
            }
        }

        #endregion

        #region  - Methoden (privat) -

        protected override void OnSourceInitialized(EventArgs e)
        {
            base.OnSourceInitialized(e);

            var source = (HwndSource) PresentationSource.FromVisual(this);
            WS ws = source.Handle.GetWindowLong();
            WSEX wsex = source.Handle.GetWindowLongEx();

            //ws |= WS.POPUP;
            wsex ^= WSEX.APPWINDOW;
            wsex |= WSEX.NOACTIVATE;

            source.Handle.SetWindowLong(ws);
            source.Handle.SetWindowLongEx(wsex);
            source.AddHook(WndProc);

            handle = source.Handle;
        }

        private bool PointHitsUpperEdge(Point p)
        {
            return new Rect(0, 0, ActualWidth, edgeSize).Contains(p);
        }

        private bool PointHitsBottomEdge(Point p)
        {
            return new Rect(0, ActualHeight - edgeSize, ActualWidth, edgeSize).Contains(p);
        }

        private bool PointHitsLeftEdge(Point p)
        {
            return new Rect(0, 0, edgeSize - glowSize, ActualHeight).Contains(p);
        }

        private bool PointHitsRightEdge(Point p)
        {
            return new Rect(Width - edgeSize + glowSize, 0, edgeSize - glowSize, ActualHeight).Contains(p);
        }

        private Cursor GetWindowEdgeCursor(Window owner, Point p, GlowDirection glowDirection)
        {
            if (owner.ResizeMode == ResizeMode.NoResize || owner.ResizeMode == ResizeMode.CanMinimize)
            {
                return owner.Cursor;
            }

            switch (glowDirection)
            {
                default:
                case GlowDirection.Left:
                case GlowDirection.Right:
                    if (PointHitsUpperEdge(p))
                    {
                        return glowDirection == GlowDirection.Left ? Cursors.SizeNWSE : Cursors.SizeNESW;
                    }
                    if (PointHitsBottomEdge(p))
                    {
                        return glowDirection == GlowDirection.Left ? Cursors.SizeNESW : Cursors.SizeNWSE;
                    }
                    return Cursors.SizeWE;

                case GlowDirection.Top:
                case GlowDirection.Bottom:
                    if (PointHitsLeftEdge(p))
                    {
                        return glowDirection == GlowDirection.Top ? Cursors.SizeNWSE : Cursors.SizeNESW;
                    }
                    if (PointHitsRightEdge(p))
                    {
                        return glowDirection == GlowDirection.Top ? Cursors.SizeNESW : Cursors.SizeNWSE;
                    }
                    return Cursors.SizeNS;
            }
        }

        private HitTestValues GetWindowHitTestValue(Point p, GlowDirection glowDirection)
        {
            switch (glowDirection)
            {
                default:
                case GlowDirection.Left:
                case GlowDirection.Right:
                    if (PointHitsUpperEdge(p))
                    {
                        return glowDirection == GlowDirection.Left ? HitTestValues.HTTOPLEFT : HitTestValues.HTTOPRIGHT;
                    }
                    if (PointHitsBottomEdge(p))
                    {
                        return glowDirection == GlowDirection.Left
                            ? HitTestValues.HTBOTTOMLEFT
                            : HitTestValues.HTBOTTOMRIGHT;
                    }
                    return glowDirection == GlowDirection.Left ? HitTestValues.HTLEFT : HitTestValues.HTRIGHT;

                case GlowDirection.Top:
                case GlowDirection.Bottom:
                    if (PointHitsLeftEdge(p))
                    {
                        return glowDirection == GlowDirection.Top ? HitTestValues.HTTOPLEFT : HitTestValues.HTBOTTOMLEFT;
                    }
                    if (PointHitsRightEdge(p))
                    {
                        return glowDirection == GlowDirection.Top
                            ? HitTestValues.HTTOPRIGHT
                            : HitTestValues.HTBOTTOMRIGHT;
                    }
                    return glowDirection == GlowDirection.Top ? HitTestValues.HTTOP : HitTestValues.HTBOTTOM;
            }
        }

        private void UpdateCore()
        {
            if (ownerHandle == IntPtr.Zero)
            {
                ownerHandle = new WindowInteropHelper(Owner).Handle;
            }

            NativeMethods.SetWindowPos(
                handle,
                ownerHandle,
                (int) (getLeft(DpiFactor)),
                (int) (getTop(DpiFactor)),
                (int) (getWidth(DpiFactor)),
                (int) (getHeight(DpiFactor)),
                SWP.NOACTIVATE);
        }

        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            if (msg == (int) WM.MOUSEACTIVATE)
            {
                handled = true;
                return new IntPtr(3);
            }

            if (msg == (int) WM.LBUTTONDOWN)
            {
                var pt = new Point((int) lParam & 0xFFFF, ((int) lParam >> 16) & 0xFFFF);

                NativeMethods.PostMessage(ownerHandle, (uint) WM.NCLBUTTONDOWN, (IntPtr) getHitTestValue(pt),
                    IntPtr.Zero);
            }
            if (msg == (int) WM.NCHITTEST)
            {
                var ptScreen = new Point((int) lParam & 0xFFFF, ((int) lParam >> 16) & 0xFFFF);
                Point ptClient = PointFromScreen(ptScreen);
                Cursor cursor = getCursor(ptClient);
                if (cursor != Cursor) Cursor = cursor;
            }

            return IntPtr.Zero;
        }

        #endregion

        private const double edgeSize = 20.0;
        private const double glowSize = 9.0;
    }
}